package cn.android666.net.util.download

import android.content.Context
import android.os.Environment
import android.os.Handler
import android.os.Looper
import cn.android666.net.util.Nice
import okhttp3.*
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import timber.log.Timber
import java.io.File
import java.io.IOException
import java.io.RandomAccessFile
import java.net.SocketTimeoutException
import java.util.concurrent.TimeUnit


/**
 * OkHttp实现的多线程、断点续传文件下载器，每一个文件下载都对应一个新的FileDownloader对象，文件保存在外部存储专属目录中的 Download 文件夹：/sdcard/Android/data/com.example/files/Download/
 */
class FileDownloader(
    /** 文件下载地址 */
    private var downloadUrl: String,
    /** 下载结果回调 */
    private val callback: DownloadCallback,
    /** 指定一个文件分成几部分下载（也就是用几个线程下载） */
    private var rangeCount: Int = 5,
    /** 文件名称，用于下载时显示文件的名称 */
    private val fileName: String,
    /** 文件保存目录 */
    private val fileSaveDir: File,
    /** 保存文件时使用的文件名，可不传，不传的话将使用下载地址中的文件名称 */
    private val saveFileName: String? = null): RangeDownloadCallback {


    // 就算网速慢，连接的到服务器的时间也是很快的，但是网速慢时读写的速度就会很慢，超时时间很有可能大于10秒的，所以读写时间尽量设置大一点。
    // 就算网速达到1M/s，5个线程同时下也有可能有个别线程读流时超时超过10秒的，所以读写的超时时间要长一点
    private var okHttpClient: OkHttpClient = Nice.defaultOkHttpClient.newBuilder()
        .connectTimeout(10, TimeUnit.SECONDS)
        .readTimeout(60, TimeUnit.SECONDS)
        .writeTimeout(60, TimeUnit.SECONDS)
        .build()

    private val uiHandler: Handler = Handler(Looper.getMainLooper())

    /** 用于保存每个线程的下载对象 */
    private val rangeLoaderList = mutableListOf<RangeDownloader>()
    /** 用于判断是否正在下载 */
    var isDownloading = false
    /** 文件总大小 */
    private var fileSize = 0L
    /** 保存下载文件的目标文件 */
    private lateinit var destFile: File
    private lateinit var tempFile: File
    /** 所有线程总共下载大小 */
    private var totalDownloadSize = 0L
    /** 开始下载的大小，用于计算每秒的下载速度 */
    private var startSize = 0L
    /** 开始下载的时间，用于计算每秒的下载速度 */
    private var startTime = 0L
    /** 下载速度 */
    private var downloadSpeed = 0L

    /** 停止下载 */
    @Synchronized
    fun stopDownload() {
        isDownloading = false
        rangeLoaderList.forEach { it.stopDownloadRange() }
        rangeLoaderList.clear()
    }

    /** 开始下载，如果之前没下载完成，则会在之前的基础上接着下载（断点续传） */
    fun startDownload() {
        // TODO 下载要放到服务里做，要实现后台下载
        if (!isUrlValid()) return
        val fileSaveName = saveFileName ?: downloadUrl.substring(downloadUrl.lastIndexOf("/") + 1)
        destFile = File(fileSaveDir, fileSaveName)           // 用于保下载文件的文件
        tempFile = File(fileSaveDir, "${fileSaveName}.temp") // 用于保存每个线程下载进度的临时文件
        uiHandler.post { callback.onPreDownload(fileName) }

        getFileInfoOnServer(downloadUrl) { isSuccess, failMsg, fileSize, isSupportRange ->
            if (!isSuccess){
                Timber.w("获取文件信息失败：$failMsg")
                onFail(failMsg)
                return@getFileInfoOnServer Unit
            }

            this.fileSize = fileSize

            if (!isSupportRange) {
                Timber.w("${downloadUrl}不支持断点续传，将使用单线程下载")
                // 如果不支持部分下载，则使用单线程
                rangeCount = 1
                // 如果不支持分部下载，则每次下载时都要从0开始下载
                RandomAccessFile(tempFile, "rw").use { it.writeLong(0) } // 模式中的s为表示连文件的元数据也写入文件，比如照片的大小、拍摄时间等
            }

            if (destFile.exists() && destFile.length() == fileSize && !tempFile.exists()) { // apk 以前就已经下载好了
                uiHandler.post { callback.onDownloaded(destFile.absolutePath) }
            } else {
                directDownload()
            }
            Unit
        }


    }

    /** 重新下载，不管之前是下载完成了，还是下载了一部分，都删除掉重新下载 */
    fun reDownload() {
        if (!isUrlValid()) return
        if (destFile.exists()) destFile.delete()
        if (tempFile.exists()) tempFile.delete()
        uiHandler.post { callback.onPreDownload(fileName) }
        directDownload()
    }

    /** 判断下载的url是否有效 */
    private fun isUrlValid(): Boolean {
        Timber.i("fileDownloadUrl = $downloadUrl")
        return if (downloadUrl.toHttpUrlOrNull() == null) {
            uiHandler.post {  callback.onDownloadFail("下载文件的url无效，url = $downloadUrl") }
            false
        } else {
            true
        }
    }

    /** 直接下载，即不管之前有没有下载好，都直接下载 */
    private fun directDownload() {
//        downloadUrl = "http://10.238.113.50:8088/emergency_file/apk/1583891244766_Railway_4G_Platform_phone.apk"
//        downloadUrl = "http://gdown.baidu.com/data/wisegame/868c302a2b1a42c9/kugouyinle_10101.apk"
//        downloadUrl = "http://shouji.360tpcdn.com/171206/20c98cbacc3de69a006b5261d46294b8/com.joyworks.boluofan_421.apk"

        isDownloading = true
        rangeLoaderList.clear()
        totalDownloadSize = 0L
        startTime = System.currentTimeMillis()

        try {
            uiHandler.post { callback.onDownloadStart(fileName, fileSize) }

            if (!destFile.exists()) {
                // 创建一个和服务器文件大小一样的文件，用于保存下载的文件
                RandomAccessFile(destFile, "rw").use { it.setLength(fileSize) }
            } else {
                if (destFile.length() != fileSize) {
                    destFile.delete()
                    if (tempFile.exists()) tempFile.delete()
                    RandomAccessFile(destFile, "rw").use { it.setLength(fileSize) }
                    Timber.e("文件大小不一样了，说明服务器上同名同版本的文件竟然上传了两个不一样的文件，删除重新下")
                }
            }

            if (!tempFile.exists()) {
                // 创建临时文件，用来记录每个线程的下载位置
                RandomAccessFile(tempFile, "rw").use { raf ->
                    repeat(rangeCount) {
                        raf.writeLong(0)
                    }
                }
            }

            RandomAccessFile(tempFile, "rw").use { raf ->
                var totalDownload = 0L
                repeat(rangeCount) {
                    totalDownload += raf.readLong()       // 计算之前下载好的总大小
                    startSize = totalDownload
                }
                onLoading(totalDownload)
            }

            // 计算每个线程要下载的长度，最后一个线程可能不需要下载这么多，但是没关系，指定多了，服务器也不会有多内容返回的
            val rangeSize = (fileSize + rangeCount - 1) / rangeCount // 每个线程需要下载的大小
            var endRangeSize = 0L // 最后一部分的大小，不能使用rangeSize，这会导致如果最后一部分先下载完了，此时断开下载，续传时会继续下载最后一部分而出错

            Timber.e("文件大小为：$fileSize, 平均rangeSize为：$rangeSize")
            synchronized(this) {
                for (rangeIndex in 0 until rangeCount) {
                    if (fileSize % rangeCount > 0 && rangeIndex == rangeCount - 1) {
                        // 如果文件大小不能正好平分，则最后一部分不要使用平均值
                        endRangeSize = fileSize - rangeSize * (rangeCount - 1)
                        Timber.e("rangeIndex: $rangeIndex 的size为: $endRangeSize")
                    }
                    rangeLoaderList.add(RangeDownloader(okHttpClient, downloadUrl, destFile, rangeIndex, rangeSize, endRangeSize, this).apply { startDownloadRange() })
                }
            }
        } catch (e: Exception) {
            if (destFile.exists()) destFile.delete()
            if (tempFile.exists()) tempFile.delete()
            Timber.e(e, "下载文件失败，读写文件时出现异常")
            onFail("下载文件失败，读写文件时出现异常，${e.javaClass.simpleName}: ${e.message}")
        }
    }

    /**
     * 获取要下载的文件在服务器上的信息
     * @param callback 返回文件在服务器上的信息, Boolean 获取信息是否成功，String，获取信息失败的原因（如果获取失败的话），文件大小，文件是否支持部分下载
     */
    private fun getFileInfoOnServer(downloadUrl: String, callback: (Boolean, String, Long, Boolean) -> Unit?) {
        val request = Request.Builder().url(downloadUrl).build()
        okHttpClient.newCall(request).enqueue(object: Callback {
            override fun onFailure(call: Call, e: IOException) {
                val failMsg = if (e is SocketTimeoutException) {
                    "网络不稳定，获取文件大小超时"
                } else {
                    "获取文件大小时出现异常: ${e.message}"
                }
                Timber.w(e, "获取文件大小时出现异常，文件url = $downloadUrl")
                uiHandler.post { callback(false, failMsg, 0, false) }
            }

            override fun onResponse(call: Call, response: Response) {
                if (!response.isSuccessful) { uiHandler.post { callback(false, "获取文件大小失败，响应码：${response.code}", 0, false) }; return }
                val fileSize = response.body.contentLength()
                if (fileSize <= 0) { uiHandler.post { callback(false, "获取文件大小失败，文件大小为：$fileSize", 0, false) }; return }
                val isSupportRange = response.header("Accept-Ranges") == "bytes"
                uiHandler.post { callback(true, "获取文件信息成功", fileSize, isSupportRange) }
            }
        })
    }

    override fun onLoading(currentPosition: Long) {
        synchronized(this) {
            // 因为多个线程下载都回调这里，多个线程修改同一个数据（totalDownloadSize），所以需要同步
            totalDownloadSize += currentPosition
            if (System.currentTimeMillis() - startTime >= 100) {
                // 每100毫秒计算一次下载速度
                startTime = System.currentTimeMillis()
                downloadSpeed = (totalDownloadSize - startSize) * 10 // 因为计算的是100毫秒的下载速度，乘以10就等到1秒钟的大概下载速度了
                startSize = totalDownloadSize

                // 每100毫秒更新一次，预防下载速度太快时会导致刷新UI过多，这也会把界面卡住的
                uiHandler.post { callback.onLoading(totalDownloadSize, downloadSpeed) }
            }

            if (totalDownloadSize > fileSize) { // 这种情况应该不会发生
                if (destFile.exists()) destFile.delete()
                if (tempFile.exists()) tempFile.delete()
                Timber.e("下载异常，总下载大小竟然超过了文件大小：totalDownloadSize = $totalDownloadSize, fileSize = $fileSize")
                onFail("下载异常：totalDownloadSize = $totalDownloadSize, fileSize = $fileSize")
                return
            }
            if (totalDownloadSize == fileSize) {
                Timber.i("整个文件下载完成：${destFile.absolutePath}")
                uiHandler.post { callback.onDownloadCompleted(destFile.absolutePath) }
                isDownloading = false
                if (tempFile.exists()) tempFile.delete()
            }
        }
    }

    override fun onFail(errorMsg: String) {
        // 取消所有范围下载中的回调，以防多个范围下载同时报错时导致一个下载弹出多次错误提示
        stopDownload()
        uiHandler.post { callback.onDownloadFail(errorMsg) }
    }

}